//! GraphQL schema generator
//! 
//! This module provides functionality to dynamically generate GraphQL schemas
//! from database metadata.

use crate::{
    models::DatabaseMetadata,
    utils::{TypeMapper, NamingConverter},
};

use tracing::{debug, info};

/// GraphQL schema generator
pub struct SchemaGenerator {
    type_mapper: TypeMapper,
    naming: NamingConverter,
}

impl SchemaGenerator {
    /// Create a new schema generator
    pub fn new() -> Self {
        Self {
            type_mapper: TypeMapper::new(),
            naming: NamingConverter::new(),
        }
    }
    
    /// Generate GraphQL schema from database metadata
    pub async fn generate_schema(&mut self, metadata: &DatabaseMetadata) -> crate::Result<String> {
        info!("Generating GraphQL schema from database metadata");
        
        let mut schema_parts = Vec::new();
        
        // Generate scalar types
        schema_parts.push(self.generate_scalar_types());
        
        // Generate enum types
        schema_parts.push(self.generate_enum_types());
        
        // Generate object types for each table
        for table in metadata.tables.values() {
            debug!("Generating types for table: {}", table.name);
            let object_type = self.generate_object_type(table)?;
            schema_parts.push(object_type);
            let input_type = self.generate_input_type(table)?;
            schema_parts.push(input_type);
            let filter_type = self.generate_filter_type(table)?;
            schema_parts.push(filter_type);
            let order_type = self.generate_order_type(table)?;
            schema_parts.push(order_type);
        }
        
        // Generate Query type
        let query_type = self.generate_query_type(metadata)?;
        schema_parts.push(query_type);
        
        // Generate Mutation type
        let mutation_type = self.generate_mutation_type(metadata)?;
        schema_parts.push(mutation_type);
        
        // Combine all parts
        let schema = schema_parts.join("\n\n");
        
        info!("GraphQL schema generation completed");
        Ok(schema)
    }
    
    /// Generate scalar types
    fn generate_scalar_types(&self) -> String {
        r#"# Custom scalar types
scalar DateTime
scalar Date
scalar Time
scalar JSON
scalar Upload"#.to_string()
    }
    
    /// Generate enum types
    fn generate_enum_types(&self) -> String {
        r#"# Order direction enum
enum OrderDirection {
  ASC
  DESC
}"#.to_string()
    }
    
    /// Generate object type for a table
    fn generate_object_type(&mut self, table: &crate::models::TableInfo) -> crate::Result<String> {
        let type_name = self.naming.snake_to_pascal(&table.name);
        let mut fields = Vec::new();
        
        for column in &table.columns {
            let field_name = self.naming.snake_to_camel(&column.name);
            let field_type = self.type_mapper.map_sql_to_graphql(&column.data_type);
            
            let nullable = if column.is_nullable { "" } else { "!" };
            let field_def = format!("  {}: {}{}", field_name, field_type, nullable);
            
            if let Some(comment) = &column.comment {
                fields.push(format!("  # {}", comment));
            }
            fields.push(field_def);
        }
        
        let description = if let Some(comment) = &table.comment {
            format!("\"\"\"\n{}\n\"\"\"\n", comment)
        } else {
            String::new()
        };
        
        Ok(format!(
            "{}type {} {{\n{}\n}}",
            description,
            type_name,
            fields.join("\n")
        ))
    }
    
    /// Generate input type for a table
    fn generate_input_type(&mut self, table: &crate::models::TableInfo) -> crate::Result<String> {
        let type_name = format!("{}Input", self.naming.snake_to_pascal(&table.name));
        let mut fields = Vec::new();
        
        for column in &table.columns {
            // Skip auto-increment primary keys in input
            if column.is_auto_increment && column.is_primary_key {
                continue;
            }
            
            let field_name = self.naming.snake_to_camel(&column.name);
            let field_type = self.type_mapper.map_sql_to_graphql(&column.data_type);
            
            // Make all input fields optional except required non-nullable fields
            let nullable = if column.is_nullable || column.default_value.is_some() { 
                "" 
            } else { 
                "!" 
            };
            let field_def = format!("  {}: {}{}", field_name, field_type, nullable);
            
            if let Some(comment) = &column.comment {
                fields.push(format!("  # {}", comment));
            }
            fields.push(field_def);
        }
        
        Ok(format!(
            "input {} {{\n{}\n}}",
            type_name,
            fields.join("\n")
        ))
    }
    
    /// Generate filter input type for a table
    fn generate_filter_type(&mut self, table: &crate::models::TableInfo) -> crate::Result<String> {
        let type_name = format!("{}Filter", self.naming.snake_to_pascal(&table.name));
        let mut fields = Vec::new();
        
        for column in &table.columns {
            let field_name = self.naming.snake_to_camel(&column.name);
            let field_type = self.type_mapper.map_sql_to_graphql(&column.data_type);
            
            // Basic equality
            fields.push(format!("  {}: {}", field_name, field_type));
            fields.push(format!("  {}_eq: {}", field_name, field_type));
            fields.push(format!("  {}_ne: {}", field_name, field_type));
            fields.push(format!("  {}_in: [{}!]", field_name, field_type));
            fields.push(format!("  {}_not_in: [{}!]", field_name, field_type));
            
            // String-specific filters
            if field_type == "String" {
                fields.push(format!("  {}_contains: String", field_name));
                fields.push(format!("  {}_starts_with: String", field_name));
                fields.push(format!("  {}_ends_with: String", field_name));
                fields.push(format!("  {}_regex: String", field_name));
            }
            
            // Numeric filters
            if matches!(field_type.as_str(), "Int" | "Float") {
                fields.push(format!("  {}_gt: {}", field_name, field_type));
                fields.push(format!("  {}_gte: {}", field_name, field_type));
                fields.push(format!("  {}_lt: {}", field_name, field_type));
                fields.push(format!("  {}_lte: {}", field_name, field_type));
            }
        }
        
        // Logical operators
        fields.push(format!("  _and: [{}!]", type_name));
        fields.push(format!("  _or: [{}!]", type_name));
        fields.push(format!("  _not: {}", type_name));
        
        Ok(format!(
            "input {} {{\n{}\n}}",
            type_name,
            fields.join("\n")
        ))
    }
    
    /// Generate order input type for a table
    fn generate_order_type(&mut self, table: &crate::models::TableInfo) -> crate::Result<String> {
        let type_name = format!("{}OrderBy", self.naming.snake_to_pascal(&table.name));
        let mut fields = Vec::new();
        
        for column in &table.columns {
            let field_name = self.naming.snake_to_camel(&column.name);
            fields.push(format!("  {}: OrderDirection", field_name));
        }
        
        Ok(format!(
            "input {} {{\n{}\n}}",
            type_name,
            fields.join("\n")
        ))
    }
    
    /// Generate Query type
    fn generate_query_type(&mut self, metadata: &DatabaseMetadata) -> crate::Result<String> {
        let mut fields = Vec::new();
        
        for table in metadata.tables.values() {
            let type_name = self.naming.snake_to_pascal(&table.name);
            let field_name = self.naming.snake_to_camel(&table.name);
            let field_name_plural = self.naming.to_plural(&field_name);
            
            // Single item query
            fields.push(format!(
                "  {}: {}",
                field_name,
                type_name
            ));
            
            // List query with filters
            fields.push(format!(
                "  {}: [{}!]!",
                field_name_plural,
                type_name
            ));
            
            // List query with pagination and filters
            fields.push(format!(
                "  {}(\n    filter: {}Filter\n    orderBy: {}OrderBy\n    limit: Int\n    offset: Int\n  ): [{}!]!",
                field_name_plural,
                type_name,
                type_name,
                type_name
            ));
        }
        
        Ok(format!(
            "type Query {{\n{}\n}}",
            fields.join("\n")
        ))
    }
    
    /// Generate Mutation type
    fn generate_mutation_type(&mut self, metadata: &DatabaseMetadata) -> crate::Result<String> {
        let mut fields = Vec::new();
        
        for table in metadata.tables.values() {
            let type_name = self.naming.snake_to_pascal(&table.name);
            let field_name = self.naming.snake_to_camel(&table.name);
            
            // Create mutation
            fields.push(format!(
                "  create{}(input: {}Input!): {}!",
                type_name,
                type_name,
                type_name
            ));
            
            // Update mutation
            fields.push(format!(
                "  update{}(id: ID!, input: {}Input!): {}!",
                type_name,
                type_name,
                type_name
            ));
            
            // Delete mutation
            fields.push(format!(
                "  delete{}(id: ID!): Boolean!",
                type_name
            ));
            
            // Bulk operations
            fields.push(format!(
                "  createMany{}(input: [{}Input!]!): [{}!]!",
                type_name,
                type_name,
                type_name
            ));
            
            fields.push(format!(
                "  updateMany{}(filter: {}Filter!, input: {}Input!): Int!",
                type_name,
                type_name,
                type_name
            ));
            
            fields.push(format!(
                "  deleteMany{}(filter: {}Filter!): Int!",
                type_name,
                type_name
            ));
        }
        
        Ok(format!(
            "type Mutation {{\n{}\n}}",
            fields.join("\n")
        ))
    }
}

impl Default for SchemaGenerator {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::{TableInfo, ColumnInfo};
    
    #[test]
    fn test_schema_generator_creation() {
        let mut generator = SchemaGenerator::new();
        assert!(true); // Just test creation
    }
    
    #[test]
    fn test_scalar_types_generation() {
        let mut generator = SchemaGenerator::new();
        let scalars = generator.generate_scalar_types();
        assert!(scalars.contains("scalar DateTime"));
        assert!(scalars.contains("scalar JSON"));
    }
    
    #[test]
    fn test_enum_types_generation() {
        let generator = SchemaGenerator::new();
        let enums = generator.generate_enum_types();
        assert!(enums.contains("enum OrderDirection"));
        assert!(enums.contains("ASC"));
        assert!(enums.contains("DESC"));
    }
    
    #[tokio::test]
    async fn test_object_type_generation() {
        let mut generator = SchemaGenerator::new();
        
        let mut table = TableInfo::new("users".to_string(), "public".to_string());
        table.add_column(ColumnInfo {
            name: "id".to_string(),
            data_type: "integer".to_string(),
            is_nullable: false,
            is_primary_key: true,
            is_auto_increment: true,
            default_value: None,
            comment: Some("User ID".to_string()),
            max_length: None,
            precision: None,
            scale: None,
        });
        
        table.add_column(ColumnInfo {
            name: "name".to_string(),
            data_type: "varchar".to_string(),
            is_nullable: false,
            is_primary_key: false,
            is_auto_increment: false,
            default_value: None,
            comment: Some("User name".to_string()),
            max_length: Some(255),
            precision: None,
            scale: None,
        });
        
        let object_type = generator.generate_object_type(&table).unwrap();
        assert!(object_type.contains("type Users"));
        assert!(object_type.contains("id: Int!"));
        assert!(object_type.contains("name: String!"));
        assert!(object_type.contains("# User ID"));
        assert!(object_type.contains("# User name"));
    }
}